use std::collections::HashMap;

use regex::Regex;

fn parse_input() -> (i32, i32) {
    let input = std::fs::read_to_string("./day21/input.txt").unwrap();
    let re = Regex::new(r"[0-9]+").unwrap();
    let nums: Vec<i32> = re.find_iter(&input).map(|s| s.as_str().parse().unwrap()).collect();
    (nums[1], nums[3])
}

#[allow(unused)]
fn part1(p1: i32, p2: i32) {

    struct Die {
        point: i32,
        times: i32,
    }
    
    impl Die {
        fn new() -> Self {
            Die {
                point: 1, times: 0
            }
        }
    
        fn roll(&mut self) -> i32 {
            self.times += 1;
            let point = self.point;
    
            if self.point == 100 {
                self.point = 1;
            } else {
                self.point += 1;
            }
            
            point
        }
    }
    
    struct Player {
        position: i32,
        score: i32,
    }
    
    impl Player {
        fn new(position: i32) -> Self {
            Player { position, score: 0 }
        }
    
        fn play(&mut self, die: &mut Die) -> bool {
            let dis = die.roll() + die.roll() + die.roll();
            self.position = ((self.position - 1) + dis) % 10 + 1;
            self.score += self.position;
    
            self.score >= 1000
        }
    }

    let mut plater1 = Player::new(p1);
    let mut plater2 = Player::new(p2);
    let mut die = Die::new();

    loop {
        if plater1.play(&mut die) {
            println!("Pat1: {}", plater2.score * die.times);
            break;
        }
        if plater2.play(&mut die) {
            println!("Pat1: {}", plater1.score * die.times);
            break;
        }
    }
}

#[allow(unused)]
fn part2(p1: i32, p2: i32) {

    struct Solution {
        cache: HashMap<([i32; 2], [i32; 2], i32), (usize, usize)>,
    }

    impl Solution {
        fn new() -> Self {
            Self { cache: HashMap::new() }
        }
 
        fn set_result(&mut self, key: ([i32; 2], [i32; 2], i32), value: (usize, usize)) {
            self.cache.insert(key, value);
        }

        // Each round, a player roll 3 times, so an turn roll 6 times
        // 'wind_count' is for a roll
        fn win_count(&mut self, scores: [i32; 2], positions: [i32; 2], turn: i32) -> (usize, usize) {
            if scores[0] >= 21 {
                return (1, 0);
            } 
            if scores[1] >= 21 {
                return (0, 1);
            }

            let result = self.cache.get(&(scores, positions, turn));
            if let Some(count) = result {
                return *count; 
            }

            let (mut win_count1, mut win_count2) = (0, 0);

            // Each roll, create 3 univer
            for roll_value in 1..=3 {
                // 1 turn => 6 rolls
                // player1 => 0, 1, 2 roll
                // player2 => 3, 4, 5 roll
                let player = (turn / 3) as usize;
                let position = (positions[player] + roll_value - 1) % 10 + 1;
                let score = scores[player] + if turn % 3 == 2 { position } else { 0 };
                let (w1, w2) = self.win_count(
                    if player == 1 { [scores[0], score] } else { [score, scores[1]] },
                    if player == 1 { [positions[0], position] } else { [position, positions[1]] },
                    (turn + 1) % 6,
                );

                win_count1 += w1;
                win_count2 += w2;
            }

            self.set_result((scores, positions, turn), (win_count1, win_count2));

            (win_count1, win_count2)
        }
    
    }

    let result = Solution::new().win_count([0, 0], [p1, p2], 0);
    println!("Part2: {}", if result.0 > result.1 { result.0 } else { result.1 });
}

fn main() {
    let (p1, p2) = parse_input();
    part1(p1, p2);
    part2(p1, p2);
}
